﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IndicoInterface;
using IndicoInterface.SimpleAgendaDataModel;
using System.Net;
using System.IO;
using Utilities;

namespace DTBuilderLib
{
    /// <summary>
    /// Queue up and download, one-by-one, the talks from the
    /// agenda server.
    /// </summary>
    public class JobDownloader
    {
        /// <summary>
        /// How frequently should a progress update be served in Ticks? Defaults to 1 second.
        /// </summary>
        public int TicksBetweenProgressUpdates { get; set; }

        /// <summary>
        /// Base directory where we can write out downloads in a structured way. Defaults to the
        /// temp location + a "Agenda Downloads" subdirectory.
        /// </summary>
        public string FileDownloadLocation { get; set; }

        /// <summary>
        /// Init the object
        /// </summary>
        public JobDownloader()
        {
            TicksBetweenProgressUpdates = 1000;
            FileDownloadLocation = Path.GetTempPath() + @"\Agenda Downloads";
        }

        /// <summary>
        /// This is called each time a file download progresses. It will be called at least every
        /// 2 seconds. It will always be called with percent=1.0 when the file is done no matter
        /// how long ago since the previous call.
        /// This thread will probably not be called on the thread that you initiated the download on!
        /// </summary>
        /// <param name="meeting">The complete structure you anded to be downloaded</param>
        /// <param name="session">Session being worked on</param>
        /// <param name="talk">Talk finshed; the URL is now a local file</param>
        /// <param name="percent">The % done of this file</param>
        public delegate void DownloadFileProgress(Meeting meeting, Session session, Talk talk, float percent);

        /// <summary>
        /// This delecate is called when the download for a meeting is done. This thread will probably not
        /// be called on the thread you initiated the download on!
        /// </summary>
        /// <param name="meeting"></param>
        public delegate void DownloadMeetingComplete(Meeting meeting);

        private class DownloadTask
        {
            public Meeting _meeting;
            private DownloadFileProgress _progress;
            private DownloadMeetingComplete _done;

            public DownloadTask(DownloadFileProgress progress, DownloadMeetingComplete meeting)
            {
                _progress = progress;
                _done = meeting;
            }

            public void Progress (Meeting meeting, Session session, Talk talk, float percent)
            {
                if (_progress != null)
                {
                    _progress(meeting, session, talk, percent);
                }
            }
            public void Done(Meeting meeting)
            {
                if (_done != null)
                {
                    _done(meeting);
                }
            }
        }

        private Queue<DownloadTask> _download_tasks = new Queue<DownloadTask>();
        private bool _download_in_progress = false;

        /// <summary>
        /// Start the download of a meeting. Fire off the appropriate events as it finishes us.
        /// </summary>
        /// <param name="m"></param>
        /// <param name="filedone"></param>
        /// <param name="meetingdone"></param>
        public void StartMeetingDownload(Meeting m, DownloadFileProgress filedone, DownloadMeetingComplete meetingdone)
        {
            DownloadTask t = new DownloadTask(filedone, meetingdone) { _meeting = m};
            lock (_download_tasks)
            {
                _download_tasks.Enqueue(t);
                if (!_download_in_progress)
                {
                    System.Threading.ThreadPool.QueueUserWorkItem(o => DownloadThreadProc());
                }
            }
        }

        /// <summary>
        /// Actually do the work in the background to do the file download.
        /// </summary>
        private void DownloadThreadProc()
        {
            ///
            /// First, see if anyone else is working. If they are, then quit!
            /// 

            DownloadTask t = null;
            lock (_download_tasks)
            {
                if (_download_in_progress || _download_tasks.Count == 0)
                {
                    return;
                }
                _download_in_progress = true;
                t = _download_tasks.Dequeue();
            }

            try
            {
                DownloadMeetingDriver(t);
            }
            finally
            {
                ///
                /// Let the caller know we are done!
                /// 

                t.Done(t._meeting);

                ///
                /// Done with the download. Now we need to let others know we are done, and queue up the next guy to
                /// do some work.
                /// 

                lock (_download_tasks)
                {
                    _download_in_progress = false;
                }
                System.Threading.ThreadPool.QueueUserWorkItem(o => DownloadThreadProc());
            }

        }

        /// <summary>
        /// Does the work of downloading a single talk, synchronosly.
        /// </summary>
        /// <param name="t"></param>
        private void DownloadMeetingDriver(DownloadTask t)
        {
            foreach (var session in t._meeting.Sessions)
            {
                foreach (var talk in session.Talks)
                {
                    DownloadFile(session, talk, t);
                }
            }
        }

        /// <summary>
        /// This guy does the actual downloading of the file.
        /// </summary>
        /// <param name="session"></param>
        /// <param name="talk"></param>
        /// <param name="t"></param>
        private void DownloadFile(Session session, Talk talk, DownloadTask t)
        {
            /// If there is no talk, skip it!

            if (talk.SlideURL == null)
            {
                return;
            }

            ///
            /// Look for the token file. If it is there, then we did the download ok.
            /// 

            string download_dir = FileDownloadLocation
                + "\\" + t._meeting.Site
                + "\\Agenda-" + t._meeting.ID.ToString()
                + "\\Session-" + session.ID.ToString();
            string final_filename_token = "Talk-" + talk.ID.ToString() + ".txt";
            string token_path = download_dir + "\\" + final_filename_token;
            FileInfo tokenFile = new FileInfo(token_path);
            if (tokenFile.Exists)
            {
                FileInfo finalFile = new FileInfo(tokenFile.GetFirstLine());
                if (finalFile.Exists)
                {
                    talk.SlideURL = finalFile.FullName;
                    return;
                }
            }
                    

            ///
            /// Make sure the file exists on the server, and get the MIME type so we can assemble the final
            /// filename
            /// 

            string file_type = "";
            WebRequest getTypeRequest = WebRequest.Create(talk.SlideURL);
            getTypeRequest.Method = "HEAD";
            try
            {
                using (WebResponse res = getTypeRequest.GetResponse())
                {
                    if (res.ContentType.Contains("pdf"))
                    {
                        file_type = ".pdf";
                    }
                    else if (res.ContentType.Contains("powerpoint"))
                    {
                        file_type = ".ppt";
                    }
                    else if (res.ContentType.Contains("postscript"))
                    {
                        file_type = ".ps";
                    }
                    else
                    {
                        Logger.LogErrorMessage("UnkownFileType", "Talk URL pointing to " + talk.SlideURL + " has mime type " + res.ContentType + " - we can't deal.");
                        talk.SlideURL = null;
                        return;
                    }
                }
            }
            catch (Exception e)
            {
                Logger.LogErrorMessage("URLHeaderReq", "Failed to get header for " + talk.SlideURL + " - " + e.Message);

                talk.SlideURL = ""; // SIgn of an error!
                t.Progress(t._meeting, session, talk, 100.0F);

                return;
            }

            /// Construct the final filename and the dummy filename to use while downloading.
            if (!Directory.Exists(download_dir))
            {
                Directory.CreateDirectory(download_dir);
            }

            string final_filename = "Talk-" + talk.ID.ToString() + file_type;
            string temp_filename = "Temp-" + final_filename;

            string final_path = download_dir + "\\" + final_filename;
            string temp_path = download_dir + "\\" + temp_filename;

            ///
            /// Write the token file so we don't have to do this if we are going fast!
            /// 

            tokenFile.WriteSingleLine(final_path);

            ///
            /// If the file is already there, then bug right-outta here!
            /// 

            if (File.Exists(final_path))
            {
                talk.SlideURL = final_path;
                t.Progress(t._meeting, session, talk, 100.0F);
                return;
            }

            ///
            /// Ok -- we have to access the web now and download the file. This
            /// could be a very large file, so don't try to slurp it into
            /// memory all at once, though that might be tempting. Also, treat it
            /// as bianry (which it probably is!). We can get a 404 here, btw. That is,
            /// the file is not found on the web server. Deal with that. No file returned...
            /// 

            WebRequest req = WebRequest.Create(talk.SlideURL);
            try
            {
                using (WebResponse res = req.GetResponse())
                {
                    float filesize = res.ContentLength; // Saftey in case we can't get it.
                    Stream datastream = res.GetResponseStream();
                    datastream.ReadTimeout = TicksBetweenProgressUpdates*5;

                    const int datasize = 1024 * 16;
                    byte[] data = new byte[datasize];
                    int bytesread = 0;

                    using (Stream output = File.Create(temp_path))
                    {

                        bool done = false;
                        while (!done)
                        {
                            int readbytes = datastream.Read(data, 0, datasize);
                            if (readbytes == 0)
                            {
                                done = true;
                            }
                            else
                            {
                                bytesread = bytesread + readbytes;
                                t.Progress(t._meeting, session, talk, (bytesread / filesize) * 100.0F);
                                output.Write(data, 0, readbytes);
                            }
                        }

                        output.Close();
                    }

                    datastream.Close();
                }
            }
            catch (Exception e)
            {
                Logger.LogErrorMessage("URLDownload", "Failed to get header for " + talk.SlideURL + " - " + e.Message);

                talk.SlideURL = ""; // SIgn of an error!
                t.Progress(t._meeting, session, talk, 100.0F);
                return;
            }

            ///
            /// And rename the file
            /// 

            File.Move(temp_path, final_path);

            ///
            /// Done. Let them know...
            /// 

            talk.SlideURL = final_path;
            t.Progress(t._meeting, session, talk, 100.0F);

        }

        /// <summary>
        /// A simple download interface -- if you don't care about blocking... :-)
        /// </summary>
        /// <param name="meeting"></param>
        /// <returns></returns>
        public Meeting DownloadMeeting(Meeting meeting)
        {
            DownloadTask t = new DownloadTask(null, null) { _meeting = meeting};
            DownloadMeetingDriver(t);
            return t._meeting;
        }
    }
}
